"""
Async pattern analysis for TypeScript code.

Analyzes async/await patterns, Promise usage, callback patterns, and concurrent
execution patterns in TypeScript applications.
"""

from typing import Dict, List, Any, Optional, Set, Tuple
from collections import defaultdict, deque
import re

from ...core.error_handler import AnalysisError, log_info


class TypeScriptAsyncAnalyzer:
    """Analyzes async patterns and concurrent execution in TypeScript code."""
    
    def __init__(self):
        """Initialize the async analyzer."""
        self.async_functions = []
        self.promise_chains = []
        self.callback_patterns = []
        self.concurrent_patterns = []
    
    def analyze_async_patterns(self, file_content: str, file_path: str) -> Dict[str, Any]:
        """Analyze async patterns in TypeScript code."""
        try:
            async_functions = self._extract_async_functions(file_content)
            await_expressions = self._extract_await_expressions(file_content)
            promise_patterns = self._analyze_promise_patterns(file_content)
            callback_patterns = self._detect_callback_patterns(file_content)
            concurrent_patterns = self._detect_concurrent_patterns(file_content)
            error_handling = self._analyze_async_error_handling(file_content)
            
            return {
                'file_path': file_path,
                'async_functions': async_functions,
                'await_expressions': await_expressions,
                'promise_patterns': promise_patterns,
                'callback_patterns': callback_patterns,
                'concurrent_patterns': concurrent_patterns,
                'error_handling': error_handling,
                'async_metrics': self._calculate_async_metrics(
                    async_functions, await_expressions, promise_patterns, 
                    callback_patterns, concurrent_patterns
                )
            }
            
        except Exception as e:
            raise AnalysisError(f"Failed to analyze async patterns in {file_path}: {str(e)}")
    
    def _extract_async_functions(self, content: str) -> List[Dict[str, Any]]:
        """Extract async function declarations and expressions."""
        async_functions = []
        
        # Async function patterns
        async_patterns = [
            r'async\s+function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^{]+))?\s*\{',
            r'const\s+(\w+)\s*=\s*async\s*\(([^)]*)\)\s*(?::\s*([^=]+))?\s*=>\s*\{',
            r'const\s+(\w+)\s*=\s*async\s*\(([^)]*)\)\s*(?::\s*([^=]+))?\s*=>\s*',
            r'(\w+)\s*:\s*async\s*\(([^)]*)\)\s*(?::\s*([^=]+))?\s*=>\s*\{',  # Method shorthand
            r'async\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^{]+))?\s*\{',  # Method in class
        ]
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            line = line.strip()
            
            for pattern in async_patterns:
                match = re.search(pattern, line)
                if match:
                    func_name = match.group(1)
                    parameters = match.group(2)
                    return_type = match.group(3)
                    
                    # Get function body for analysis
                    body_lines = self._extract_function_body(lines, line_num - 1)
                    await_count = sum(1 for body_line in body_lines if 'await' in body_line)
                    
                    async_functions.append({
                        'name': func_name,
                        'parameters': self._parse_parameters(parameters) if parameters else [],
                        'return_type': return_type.strip() if return_type else None,
                        'line': line_num,
                        'await_count': await_count,
                        'body_lines': len(body_lines),
                        'pattern_type': self._classify_async_pattern(line),
                        'raw': line
                    })
                    break
        
        return async_functions
    
    def _extract_await_expressions(self, content: str) -> List[Dict[str, Any]]:
        """Extract await expressions and their contexts."""
        await_expressions = []
        
        # Await patterns
        await_patterns = [
            r'await\s+([^;,)\n]+)',
            r'const\s+\{([^}]+)\}\s*=\s*await\s+([^;,)\n]+)',  # Destructuring
            r'const\s+(\w+)\s*=\s*await\s+([^;,)\n]+)',
            r'return\s+await\s+([^;,)\n]+)',
            r'throw\s+await\s+([^;,)\n]+)',
        ]
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            for pattern in await_patterns:
                matches = re.finditer(pattern, line)
                for match in matches:
                    await_expr = match.group(1) if len(match.groups()) == 1 else match.group(-1)
                    
                    await_expressions.append({
                        'expression': await_expr.strip(),
                        'line': line_num,
                        'context': line.strip(),
                        'in_try_block': self._is_in_try_block(lines, line_num - 1),
                        'in_loop': self._is_in_loop(lines, line_num - 1),
                        'pattern_type': self._classify_await_pattern(match.group(0)),
                        'potential_issues': self._detect_await_issues(await_expr, line)
                    })
        
        return await_expressions
    
    def _analyze_promise_patterns(self, content: str) -> Dict[str, Any]:
        """Analyze Promise usage patterns."""
        promise_patterns = {
            'promise_constructors': self._extract_promise_constructors(content),
            'promise_chains': self._extract_promise_chains(content),
            'promise_combinators': self._extract_promise_combinators(content),
            'promise_methods': self._extract_promise_methods(content)
        }
        
        return promise_patterns
    
    def _extract_promise_constructors(self, content: str) -> List[Dict[str, Any]]:
        """Extract Promise constructor usage."""
        constructors = []
        
        # Promise constructor patterns
        constructor_patterns = [
            r'new\s+Promise\s*<([^>]+)>\s*\(\s*\(([^)]+)\)\s*=>\s*\{',
            r'new\s+Promise\s*\(\s*\(([^)]+)\)\s*=>\s*\{',
        ]
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            for pattern in constructor_patterns:
                match = re.search(pattern, line)
                if match:
                    type_param = match.group(1) if len(match.groups()) > 1 and match.group(1) else None
                    executor_params = match.group(-1)
                    
                    constructors.append({
                        'type_parameter': type_param,
                        'executor_parameters': executor_params,
                        'line': line_num,
                        'context': line.strip()
                    })
        
        return constructors
    
    def _extract_promise_chains(self, content: str) -> List[Dict[str, Any]]:
        """Extract Promise chain patterns (.then().catch())."""
        chains = []
        
        # Find .then() and .catch() chains
        chain_patterns = [
            r'\.then\s*\(',
            r'\.catch\s*\(',
            r'\.finally\s*\(',
        ]
        
        lines = content.split('\n')
        i = 0
        while i < len(lines):
            line = lines[i].strip()
            
            # Look for start of promise chain
            if any(re.search(pattern, line) for pattern in chain_patterns):
                chain_start = i
                chain_methods = []
                
                # Follow the chain across multiple lines
                current_line = i
                while current_line < len(lines):
                    current_line_content = lines[current_line].strip()
                    
                    for pattern in chain_patterns:
                        if re.search(pattern, current_line_content):
                            method_name = pattern.replace(r'\.', '').replace(r'\s*\(', '')
                            chain_methods.append({
                                'method': method_name,
                                'line': current_line + 1
                            })
                    
                    # Check if chain continues on next line
                    if current_line_content.endswith('.') or \
                       (current_line + 1 < len(lines) and 
                        lines[current_line + 1].strip().startswith('.')):
                        current_line += 1
                    else:
                        break
                
                if len(chain_methods) > 1:
                    chains.append({
                        'start_line': chain_start + 1,
                        'end_line': current_line + 1,
                        'methods': chain_methods,
                        'length': len(chain_methods)
                    })
                
                i = current_line + 1
            else:
                i += 1
        
        return chains
    
    def _extract_promise_combinators(self, content: str) -> List[Dict[str, Any]]:
        """Extract Promise combinator usage (Promise.all, Promise.race, etc.)."""
        combinators = []
        
        # Promise combinator patterns
        combinator_patterns = [
            r'Promise\.all\s*\(',
            r'Promise\.allSettled\s*\(',
            r'Promise\.race\s*\(',
            r'Promise\.any\s*\(',
            r'Promise\.resolve\s*\(',
            r'Promise\.reject\s*\(',
        ]
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            for pattern in combinator_patterns:
                matches = re.finditer(pattern, line)
                for match in matches:
                    method_name = match.group(0).replace('Promise.', '').replace('(', '').strip()
                    
                    combinators.append({
                        'method': method_name,
                        'line': line_num,
                        'context': line.strip(),
                        'concurrent': method_name in ['all', 'allSettled', 'race', 'any']
                    })
        
        return combinators
    
    def _extract_promise_methods(self, content: str) -> List[Dict[str, Any]]:
        """Extract other Promise-related method calls."""
        methods = []
        
        # Promise method patterns
        method_patterns = [
            r'\.toPromise\s*\(',
            r'\.promisify\s*\(',
            r'util\.promisify\s*\(',
        ]
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            for pattern in method_patterns:
                matches = re.finditer(pattern, line)
                for match in matches:
                    method_name = match.group(0).replace('.', '').replace('(', '').strip()
                    
                    methods.append({
                        'method': method_name,
                        'line': line_num,
                        'context': line.strip()
                    })
        
        return methods
    
    def _detect_callback_patterns(self, content: str) -> List[Dict[str, Any]]:
        """Detect callback patterns and potential callback hell."""
        callback_patterns = []
        
        # Callback patterns
        patterns = [
            r'function\s*\([^)]*callback[^)]*\)',
            r'\(\s*[^)]*callback[^)]*\)\s*=>\s*\{',
            r'\.on\s*\(\s*[\'"](\w+)[\'"],\s*\(',
            r'\.addEventListener\s*\(\s*[\'"](\w+)[\'"],\s*\(',
            r'setTimeout\s*\(',
            r'setInterval\s*\(',
        ]
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            for pattern in patterns:
                matches = re.finditer(pattern, line)
                for match in matches:
                    callback_type = self._classify_callback_pattern(match.group(0))
                    
                    callback_patterns.append({
                        'type': callback_type,
                        'line': line_num,
                        'context': line.strip(),
                        'nesting_level': self._calculate_nesting_level(lines, line_num - 1)
                    })
        
        return callback_patterns
    
    def _detect_concurrent_patterns(self, content: str) -> List[Dict[str, Any]]:
        """Detect concurrent execution patterns."""
        concurrent_patterns = []
        
        # Concurrent patterns
        patterns = [
            r'Promise\.all\s*\(\s*\[',
            r'Promise\.allSettled\s*\(\s*\[',
            r'Promise\.race\s*\(\s*\[',
            r'await\s+Promise\.all',
            r'await\s+Promise\.allSettled',
            r'await\s+Promise\.race',
            r'async\s+function\s*\*',  # Async generators
            r'for\s+await\s*\(',  # Async iteration
        ]
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            for pattern in patterns:
                matches = re.finditer(pattern, line)
                for match in matches:
                    pattern_type = self._classify_concurrent_pattern(match.group(0))
                    
                    concurrent_patterns.append({
                        'type': pattern_type,
                        'line': line_num,
                        'context': line.strip(),
                        'potential_parallelism': self._assess_parallelism_potential(match.group(0), line)
                    })
        
        return concurrent_patterns
    
    def _analyze_async_error_handling(self, content: str) -> Dict[str, Any]:
        """Analyze error handling in async code."""
        error_handling = {
            'try_catch_blocks': self._extract_try_catch_blocks(content),
            'promise_catch_handlers': self._extract_promise_catch_handlers(content),
            'unhandled_promises': self._detect_unhandled_promises(content),
            'error_propagation': self._analyze_error_propagation(content)
        }
        
        return error_handling
    
    def _extract_try_catch_blocks(self, content: str) -> List[Dict[str, Any]]:
        """Extract try-catch blocks that handle async operations."""
        try_catch_blocks = []
        
        lines = content.split('\n')
        i = 0
        while i < len(lines):
            line = lines[i].strip()
            
            if line.startswith('try'):
                try_start = i
                brace_count = 0
                has_await = False
                
                # Find the end of try block
                j = i
                while j < len(lines):
                    current_line = lines[j]
                    brace_count += current_line.count('{') - current_line.count('}')
                    
                    if 'await' in current_line:
                        has_await = True
                    
                    if 'catch' in current_line and brace_count <= 1:
                        try_end = j
                        break
                    j += 1
                else:
                    i += 1
                    continue
                
                if has_await:
                    try_catch_blocks.append({
                        'try_start': try_start + 1,
                        'catch_start': try_end + 1,
                        'has_await': True,
                        'lines_covered': try_end - try_start + 1
                    })
                
                i = try_end + 1
            else:
                i += 1
        
        return try_catch_blocks
    
    def _extract_promise_catch_handlers(self, content: str) -> List[Dict[str, Any]]:
        """Extract .catch() handlers in promise chains."""
        catch_handlers = []
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            if '.catch(' in line:
                catch_handlers.append({
                    'line': line_num,
                    'context': line.strip(),
                    'chained': '.then(' in line or line.strip().startswith('.')
                })
        
        return catch_handlers
    
    def _detect_unhandled_promises(self, content: str) -> List[Dict[str, Any]]:
        """Detect potentially unhandled promises."""
        unhandled = []
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            # Look for promises without .catch() or await
            if ('Promise(' in line or '.then(' in line) and \
               '.catch(' not in line and 'await' not in line:
                unhandled.append({
                    'line': line_num,
                    'context': line.strip(),
                    'risk_level': 'medium'
                })
        
        return unhandled
    
    def _analyze_error_propagation(self, content: str) -> Dict[str, Any]:
        """Analyze how errors propagate through async code."""
        propagation = {
            'throw_statements': [],
            'error_rethrowing': [],
            'error_transformation': []
        }
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            if 'throw' in line:
                propagation['throw_statements'].append({
                    'line': line_num,
                    'context': line.strip(),
                    'in_catch': self._is_in_catch_block(lines, line_num - 1)
                })
        
        return propagation
    
    def _extract_function_body(self, lines: List[str], start_line: int) -> List[str]:
        """Extract function body lines."""
        body_lines = []
        brace_count = 0
        
        for i in range(start_line, len(lines)):
            line = lines[i]
            brace_count += line.count('{') - line.count('}')
            body_lines.append(line)
            
            if brace_count == 0 and '{' in line:
                break
        
        return body_lines
    
    def _parse_parameters(self, param_string: str) -> List[Dict[str, Any]]:
        """Parse function parameters."""
        if not param_string.strip():
            return []
        
        parameters = []
        params = [p.strip() for p in param_string.split(',')]
        
        for param in params:
            parts = param.split(':')
            name = parts[0].strip()
            param_type = parts[1].strip() if len(parts) > 1 else None
            
            parameters.append({
                'name': name,
                'type': param_type,
                'optional': '?' in name
            })
        
        return parameters
    
    def _classify_async_pattern(self, line: str) -> str:
        """Classify the type of async pattern."""
        if 'async function' in line:
            return 'async_function_declaration'
        elif '=>' in line:
            return 'async_arrow_function'
        elif ':' in line and '=>' in line:
            return 'async_method_shorthand'
        else:
            return 'async_method'
    
    def _classify_await_pattern(self, await_expr: str) -> str:
        """Classify the type of await pattern."""
        if 'return await' in await_expr:
            return 'return_await'
        elif 'const' in await_expr or 'let' in await_expr or 'var' in await_expr:
            return 'assignment_await'
        elif 'throw await' in await_expr:
            return 'throw_await'
        else:
            return 'expression_await'
    
    def _classify_callback_pattern(self, callback_expr: str) -> str:
        """Classify the type of callback pattern."""
        if 'setTimeout' in callback_expr:
            return 'timer_callback'
        elif '.on(' in callback_expr or 'addEventListener' in callback_expr:
            return 'event_callback'
        elif 'callback' in callback_expr:
            return 'node_style_callback'
        else:
            return 'general_callback'
    
    def _classify_concurrent_pattern(self, pattern: str) -> str:
        """Classify the type of concurrent pattern."""
        if 'Promise.all' in pattern:
            return 'promise_all'
        elif 'Promise.race' in pattern:
            return 'promise_race'
        elif 'Promise.allSettled' in pattern:
            return 'promise_all_settled'
        elif 'for await' in pattern:
            return 'async_iteration'
        elif 'function*' in pattern:
            return 'async_generator'
        else:
            return 'concurrent_execution'
    
    def _is_in_try_block(self, lines: List[str], line_index: int) -> bool:
        """Check if line is inside a try block."""
        for i in range(line_index, -1, -1):
            line = lines[i].strip()
            if line.startswith('try'):
                return True
            elif line.startswith('catch') or line.startswith('}'):
                return False
        return False
    
    def _is_in_loop(self, lines: List[str], line_index: int) -> bool:
        """Check if line is inside a loop."""
        for i in range(line_index, -1, -1):
            line = lines[i].strip()
            if any(loop in line for loop in ['for (', 'for(', 'while (', 'while(', 'do {']):
                return True
        return False
    
    def _is_in_catch_block(self, lines: List[str], line_index: int) -> bool:
        """Check if line is inside a catch block."""
        for i in range(line_index, -1, -1):
            line = lines[i].strip()
            if line.startswith('catch'):
                return True
            elif line.startswith('try') or line.startswith('}'):
                return False
        return False
    
    def _calculate_nesting_level(self, lines: List[str], line_index: int) -> int:
        """Calculate nesting level at a given line."""
        level = 0
        for i in range(line_index + 1):
            level += lines[i].count('{') - lines[i].count('}')
        return max(0, level)
    
    def _detect_await_issues(self, await_expr: str, line: str) -> List[str]:
        """Detect potential issues with await usage."""
        issues = []
        
        if 'return await' in line and not ('Promise.all' in line or 'Promise.race' in line):
            issues.append('unnecessary_return_await')
        
        if 'await' in line and 'for (' in line:
            issues.append('await_in_loop')
        
        return issues
    
    def _assess_parallelism_potential(self, pattern: str, line: str) -> str:
        """Assess the parallelism potential of a concurrent pattern."""
        if 'Promise.all' in pattern or 'Promise.allSettled' in pattern:
            return 'high'
        elif 'Promise.race' in pattern:
            return 'medium'
        elif 'for await' in pattern:
            return 'sequential'
        else:
            return 'low'
    
    def _calculate_async_metrics(self, async_functions: List[Dict[str, Any]], 
                                await_expressions: List[Dict[str, Any]],
                                promise_patterns: Dict[str, Any],
                                callback_patterns: List[Dict[str, Any]],
                                concurrent_patterns: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Calculate metrics for async code analysis."""
        total_async_functions = len(async_functions)
        total_await_expressions = len(await_expressions)
        total_promise_chains = len(promise_patterns.get('promise_chains', []))
        total_callbacks = len(callback_patterns)
        
        # Calculate complexity metrics
        max_callback_nesting = max(
            [cb.get('nesting_level', 0) for cb in callback_patterns], 
            default=0
        )
        
        avg_awaits_per_function = (
            sum(func.get('await_count', 0) for func in async_functions) / total_async_functions
            if total_async_functions > 0 else 0
        )
        
        concurrent_operations = len([
            pattern for pattern in concurrent_patterns 
            if pattern.get('potential_parallelism') in ['high', 'medium']
        ])
        
        return {
            'total_async_functions': total_async_functions,
            'total_await_expressions': total_await_expressions,
            'total_promise_chains': total_promise_chains,
            'total_callback_patterns': total_callbacks,
            'max_callback_nesting': max_callback_nesting,
            'avg_awaits_per_function': round(avg_awaits_per_function, 2),
            'concurrent_operations': concurrent_operations,
            'async_complexity_score': self._calculate_complexity_score(
                total_async_functions, total_await_expressions, 
                max_callback_nesting, concurrent_operations
            )
        }
    
    def _calculate_complexity_score(self, async_functions: int, await_expressions: int, 
                                   max_nesting: int, concurrent_ops: int) -> float:
        """Calculate an overall async complexity score."""
        base_score = async_functions * 2 + await_expressions
        nesting_penalty = max_nesting * 3
        concurrency_bonus = concurrent_ops * 0.5  # Concurrency can reduce complexity
        
        return max(0, base_score + nesting_penalty - concurrency_bonus)


class AsyncPatternValidator:
    """Validates async patterns for best practices and potential issues."""
    
    def __init__(self):
        """Initialize the pattern validator."""
        self.validation_rules = []
        self.issues_found = []
    
    def validate_async_patterns(self, async_analysis: Dict[str, Any]) -> Dict[str, Any]:
        """Validate async patterns and identify issues."""
        validation_results = {
            'best_practice_violations': [],
            'performance_issues': [],
            'error_handling_issues': [],
            'concurrency_issues': [],
            'recommendations': []
        }
        
        # Validate different aspects
        validation_results['best_practice_violations'].extend(
            self._validate_best_practices(async_analysis)
        )
        validation_results['performance_issues'].extend(
            self._validate_performance(async_analysis)
        )
        validation_results['error_handling_issues'].extend(
            self._validate_error_handling(async_analysis)
        )
        validation_results['concurrency_issues'].extend(
            self._validate_concurrency(async_analysis)
        )
        
        # Generate recommendations
        validation_results['recommendations'] = self._generate_recommendations(
            validation_results
        )
        
        return validation_results
    
    def _validate_best_practices(self, analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Validate async best practices."""
        violations = []
        
        # Check for return await
        for await_expr in analysis.get('await_expressions', []):
            if 'unnecessary_return_await' in await_expr.get('potential_issues', []):
                violations.append({
                    'type': 'unnecessary_return_await',
                    'line': await_expr['line'],
                    'description': 'Unnecessary return await - consider returning promise directly'
                })
        
        return violations
    
    def _validate_performance(self, analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Validate async performance patterns."""
        issues = []
        
        # Check for await in loops
        for await_expr in analysis.get('await_expressions', []):
            if await_expr.get('in_loop'):
                issues.append({
                    'type': 'await_in_loop',
                    'line': await_expr['line'],
                    'description': 'Await in loop - consider Promise.all for parallel execution'
                })
        
        return issues
    
    def _validate_error_handling(self, analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Validate async error handling."""
        issues = []
        
        # Check for unhandled promises
        for unhandled in analysis.get('error_handling', {}).get('unhandled_promises', []):
            issues.append({
                'type': 'unhandled_promise',
                'line': unhandled['line'],
                'description': 'Potentially unhandled promise - add error handling'
            })
        
        return issues
    
    def _validate_concurrency(self, analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Validate concurrency patterns."""
        issues = []
        
        # This would include more sophisticated concurrency validation
        # such as race condition detection, deadlock potential, etc.
        
        return issues
    
    def _generate_recommendations(self, validation_results: Dict[str, Any]) -> List[str]:
        """Generate recommendations based on validation results."""
        recommendations = []
        
        if validation_results['performance_issues']:
            recommendations.append(
                "Consider using Promise.all() or Promise.allSettled() for parallel async operations"
            )
        
        if validation_results['error_handling_issues']:
            recommendations.append(
                "Add proper error handling with try-catch blocks or .catch() methods"
            )
        
        if validation_results['best_practice_violations']:
            recommendations.append(
                "Follow async/await best practices to improve code readability and performance"
            )
        
        return recommendations